package org.fujene.memoryblock.repli;

import static org.fujene.memoryblock.repli.ReplicateContentBlock.LookupType.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.fujene.memoryblock.MemoryBlock;
import org.fujene.structure.Constants;
import org.fujene.structure.Enumerates;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;
import org.fujene.toolbox.ContentHash;

public class ReplicateContentBlock extends MemoryBlock{
	private volatile int opt_length = 0;
	private int opt_supplement = 0;

	public static enum LookupType {
		ALL, ID, NEXT, STR, SUPP
	}

	public ReplicateContentBlock(int bodySize, int ID, boolean isAllocate) {
		super(bodySize, ID, Constants.MEMORY_TYPE_CONTENTS, isAllocate);
	}

	// P2Pバージョンの本文情報
	public class ContentsBody {
		private int from;
		private long contentID;
		private LookupPointer nextContent;
		private int strlen;
		private String str;
		private LookupPointer[] pointers;
		
		private LookupType type;

		// 読み出し用
		public ContentsBody(int from, LookupType type) {
			this.type = type;
			this.from = from;

			if (type == ALL || type == NEXT)
				this.nextContent = ByteConverter.getPtr(getBody(from, PTR));
			else
				this.nextContent = Constants.emptyPointer;

			from += PTR;
			if (type == ALL || type == ID)
				this.contentID = ByteConverter.byteToLong(getBody(from, 8));
			else
				this.contentID = -1L;

			from += 8;
			if (type == ALL || type == SUPP){
				this.pointers = new LookupPointer[opt_supplement];
				for (int i = 0; i < opt_supplement; i++) {
					this.pointers[i] = ByteConverter.getPtr(getBody(from, PTR));
					from += PTR;
				}
			}else{
				this.pointers = null;
				from += opt_supplement * PTR;
			}
			
			if (type == ALL || type == STR)
				this.strlen = ByteConverter.byteToInt(getBody(from, STRLEN));
			else
				this.strlen = 0;

			if ((type == ALL || type == STR)
					&& this.strlen > 0) {
				from += STRLEN;
				try {
					this.str = new String(getBody(from, strlen), "UTF-8");
				} catch (UnsupportedEncodingException e) {
				}
			}
		}

		// 書き込み用
		public ContentsBody(int from, String str, long ID,
				LookupPointer nextContent) {
			this.type = ALL;
			this.from = from;
			this.str = str;

			try {
				this.strlen = str.getBytes("UTF-8").length;
			} catch (UnsupportedEncodingException e) {
			}

			this.contentID = ID;

			if (nextContent != null)
				this.nextContent = nextContent;
			else
				this.nextContent = Constants.emptyPointer;
			
			this.pointers = new LookupPointer[opt_supplement];
			for (int i = 0; i < this.pointers.length; i++) {
				this.pointers[i] = Constants.emptyPointer;
			}

			try {
				bodyVacancy -= 8 + PTR + STRLEN + (opt_supplement * PTR)
						+ str.getBytes("UTF-8").length;
			} catch (UnsupportedEncodingException e1) {
			}
		}

		public LookupPointer getNextContent() {
			return nextContent;
		}

		public long getContentID() {
			return contentID;
		}

		public void setStrlen(int strlen) {
			this.strlen = strlen;
		}

		public void write(boolean overwrite) {
			int tmp = from;

			if (type == ALL
					|| (overwrite && type == NEXT))
				setBody(ByteConverter.setPtr(nextContent), tmp);
			tmp += PTR;
			
			if (type == ALL || (overwrite && type == ID))
				setBody(ByteConverter.longToByte(contentID, 8), tmp);
			tmp += 8;

			if (type == ALL
					|| (overwrite && type == SUPP))
				for (int i = 0; i < opt_supplement; i++) {
					setBody(ByteConverter.setPtr(this.pointers[i]), tmp);
					tmp += PTR;
				}
			else
				tmp += opt_supplement * PTR;
			
			if (type == ALL || (overwrite && type == STR))
				setBody(ByteConverter.intToByte(strlen, STRLEN), tmp);
			
			if (type == ALL) {
				tmp += STRLEN;
				try {
					setBody(str.getBytes("UTF-8"), tmp);
				} catch (UnsupportedEncodingException e) {
				}
			}
		}

		public String getStr() {
			return str;
		}
	}

	public String getContent(int ptr) {
		ContentsBody ptred = new ContentsBody(ptr, ALL);
		return ptred.getStr();
	}

	public IndexQueue getContentWithID(int ptr) {
		ContentsBody ptred = new ContentsBody(ptr, ALL);
		return new IndexQueue(Enumerates.INDEX, ptred.getStr(),
				ContentHash.getContentHash(ptred.getContentID()),
				ptred.getContentID(), 0);
	}
	
	public LookupPointer[] getSupplementPointer(int ptr){
		ContentsBody ptred = new ContentsBody(ptr, SUPP);
		
		return ptred.pointers;
	}
	
	public LookupPointer getNextContentPointer(int ptr) {
		ContentsBody ptred = new ContentsBody(ptr, NEXT);
		LookupPointer retPtr = ptred.getNextContent();
		if (retPtr.equals(Constants.emptyPointer))
			return null;
		else
			return retPtr;
	}

	public long getContentID(int ptr) {
		ContentsBody ptred = new ContentsBody(ptr, ID);
		return ptred.getContentID();
	}

	public void setContent(int ptr, String str, long ID,
			LookupPointer next) {
		ContentsBody newContent = new ContentsBody(ptr, str, ID, next);
		newContent.write(true);
	}

	public void setContentPointer(int ptr, LookupPointer next) {
		ContentsBody newContent = new ContentsBody(ptr, NEXT);

		newContent.nextContent = next;
		newContent.write(true);
	}
	
	public void setSupplementPointer(int ptr, LookupPointer supp, int index){
		if(index >= opt_supplement) return;
		ContentsBody newContent = new ContentsBody(ptr, SUPP);
		
		newContent.pointers[index] = supp;
		newContent.write(true);
	}

	public void delete(int ptr) {
		ContentsBody newContent = new ContentsBody(ptr, STR);

		// 文字列長さ-1で削除扱い
		newContent.setStrlen(-1);
		newContent.write(true);
	}

	// サイズ確認つき
	public int getNewPointer(String str) {
		try {
			if (this.bodyVacancy >= 8 + PTR + STRLEN
					+ str.getBytes("UTF-8").length) {
				return this.bodySize - this.bodyVacancy;
			} else {
				return -1;
			}
		} catch (UnsupportedEncodingException e) {
			return -1;
		}
	}

	public int getCount() {
		return opt_length;
	}

	@Override
	public int getOptionSize() {
		return 2;
	}

	@Override
	public byte[] getOptionByteStream() {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		
		try {
			stream.write(ByteConverter.intToByte(opt_length, 4));
			stream.write(ByteConverter.intToByte(opt_supplement, 4));
		} catch (IOException e) {
		}
		return stream.toByteArray();
	}

	@Override
	public void setOption(int option, int value) {
		if (option == 0)
			opt_length = value;
		else if(option == 1)
			opt_supplement = value;
	}

	@Override
	public String getClassName() {
		return new String("ExtendContentBlock");
	}
	
	@Override
	public byte[] getData() throws IOException {
		throw new NoSuchMethodError("Unimplemented method");
	}

	@Override
	public void setData(byte[] array) throws IOException {
		throw new NoSuchMethodError("Unimplemented method");
	}
}
